﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using nRoute.Components;
using Orktane.Components;

namespace Orktane.Controls
{
    public class Console : ConsoleBase
    {

#region Delcarations

        private const string CONSOLE_INPUT_FORMAT = "{0}{1}";
        private readonly static string CONSOLE_STARTUP_TEXT = string.Format("Silverlight Version {0}, All rights reserverd.", Environment.Version.ToString());

        private readonly List<string> _recentCommands = new List<string>();
        private int _recentCommandsIndex = -1;
        private readonly Object _lock = new object();
        private MemoryStream _memStream;
        private StreamReader _streamInReader;
        private StringBuilder _stringOutBuilder;
        private StringWriter _stringOutWriter;
        private string _consoleInputText;
        private ConsoleKeyInfo _consoleInputKey;
        
        public Console()
        {
            SetupConsole();
            SetupCommands();             
        }

#endregion

#region Properties

        public string ConsoleOutputText
        {
            get { return _stringOutBuilder.ToString(); }
        }

        public string ConsoleInputText
        {
            get
            {
                return _consoleInputText;
            }
            set
            {
                if (!string.Equals(_consoleInputText, value, StringComparison.InvariantCulture))
                {   
                    _consoleInputText = value;
                    base.NotifyPropertyChanged(() => ConsoleInputText);
                }
            }
        }

        public ConsoleKeyInfo ConsoleInputKey
        {
            get { return _consoleInputKey; }
            set
            {
                _consoleInputKey = value;
                NotifyPropertyChanged(()=> ConsoleInputKey);
            }
        }

        public ActionCommand<ConsoleKeyInfo> ReadKeyCommand { get; private set; }

        public ActionCommand<string> ReadLineCommand { get; private set; }

        public ActionCommand CancelConsoleCommand { get; private set; }

        public ActionCommand BeepReverseCommand { get; private set; }

        public ActionCommand GetPreviousInputCommand { get; private set; }

        public ActionCommand GetNextInputCommand { get; private set; }

        public ActionCommand ClearInputCommand { get; private set; }

#endregion

#region Helpers

        private void SetupConsole()
        {
            // in --> we put things in through this stream 
            _memStream = new MemoryStream();
            _streamInReader = new StreamReader(_memStream, Encoding.Unicode);

            // out --> output from the console, comes into the string builder
            _stringOutBuilder = new StringBuilder(CONSOLE_STARTUP_TEXT + Environment.NewLine + Environment.NewLine);
            _stringOutWriter = new StringWriter(_stringOutBuilder);

            // set the in out
            base.SetIn(_streamInReader);
            base.SetOut(_stringOutWriter);
        }

        private void SetupCommands()
        {
            ReadLineCommand = new ActionCommand<string>((s) => 
            {
                // basic checks
                if (InputMode != ReadInputMode.ReadLine) return;

                // corrections
                if (s == null) s = string.Empty;
                if (!string.IsNullOrEmpty(this.Prompt) && s.StartsWith(this.Prompt))
                    s = s.Substring(this.Prompt.Length);

                lock (_lock)
                {
                    // recent commands
                    if (!string.IsNullOrEmpty(s) && (_recentCommands.Count == 0 || 
                        !string.Equals(_recentCommands[_recentCommands.Count - 1], s, StringComparison.InvariantCultureIgnoreCase)))
                    {
                        _recentCommands.Add(s);
                        _recentCommandsIndex = _recentCommands.Count;
                    }

                    // we write it out to the console
                    var _scriptBytes = Encoding.Unicode.GetBytes(s);
                    var _scriptByteCount = Encoding.Unicode.GetByteCount(s);
                    _memStream.Position = 0;                    // set the position to write
                    _memStream.SetLength(_scriptByteCount);     // set the length to write
                    _memStream.Write(_scriptBytes, 0, _scriptByteCount);    // write
                    _memStream.Position = 0;                    // position for reading

                    //base.RaiseInputAvaliable();             // so someone can read through readline method

                    // we raise the action
                    if (base.ReadLineAction != null) base.ReadLineAction(s);
                    this.ConsoleInputText = this.Prompt;
                }
            });

            ReadKeyCommand = new ActionCommand<ConsoleKeyInfo>((k) =>
            {
                // basic check
                if (InputMode != ReadInputMode.ReadKey) return;

                // else
                lock (_lock)
                {
                    // we set the last key
                    //base.LastReadKey = k;
                    //base.RaiseInputAvaliable();             // so someone can read through readline method

                    // we raise the action
                    if (base.ReadKeyAction != null) ReadKeyAction(k);
                    this.ConsoleInputText = this.Prompt;
                }
            });

            CancelConsoleCommand = new ActionCommand(() =>
            {
                base.RaiseCancelKeyPress();
            });

            BeepReverseCommand = new ActionCommand(() => { });

            GetPreviousInputCommand = new ActionCommand(() =>
            {
                if (_recentCommandsIndex >= 1)
                {
                    _recentCommandsIndex -= 1;
                    this.ConsoleInputText = string.Format(CONSOLE_INPUT_FORMAT, this.Prompt,
                                                      _recentCommands[_recentCommandsIndex]);
                }
            });

            GetNextInputCommand = new ActionCommand(() =>
            {
                if (_recentCommandsIndex <= _recentCommands.Count - 1)
                {
                    _recentCommandsIndex += 1;
                    this.ConsoleInputText = string.Format(CONSOLE_INPUT_FORMAT, this.Prompt, 
                        _recentCommandsIndex >= _recentCommands.Count ? string.Empty :                   
                            _recentCommands[_recentCommandsIndex]);
                }
            });

            ClearInputCommand = new ActionCommand(() =>
            {
                this.ConsoleInputText = this.Prompt;
            });
        }

#endregion

#region Override

        protected override void OnInputReadModeChanged()
        {
            this.ReadKeyCommand.IsActive = (this.InputMode == ReadInputMode.ReadKey);
            this.ReadLineCommand.IsActive = (this.InputMode == ReadInputMode.ReadLine);
        }

        protected override void OnPromptChanged(string oldPrompt, string newPrompt)
        {
            var _newInputText = (string.IsNullOrEmpty(oldPrompt)) ? this.ConsoleInputText : 
                this.ConsoleInputText.Substring(oldPrompt.Length);
            this.ConsoleInputText = string.Format(CONSOLE_INPUT_FORMAT, newPrompt, _newInputText);
        }

        protected override void OnClearOutput()
        {
            lock (_lock)
            {
                _stringOutBuilder.Remove(0, _stringOutBuilder.Length);
                base.NotifyPropertyChanged(() => ConsoleOutputText);
            }
        }

        protected override void OnClearInput()
        {
            lock (_lock)
            {
                this.ConsoleInputText = this.Prompt;
                _memStream.SetLength(0);
            }
        }

        protected override void OnBeep()
        {
            BeepReverseCommand.Execute();
        }

        protected override void OnWrite()
        {
            if (_stringOutBuilder.Length > base.OutBufferLength)
            {
                var _excessLength = _stringOutBuilder.Length - base.OutBufferLength;
                _stringOutBuilder.Remove(0, _excessLength);
            }
            base.NotifyPropertyChanged(() => ConsoleOutputText);
            //base.RaiseOutputAvaliable();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                base.Dispose(true);
                if (_memStream != null)
                {
                    _memStream.Dispose();
                    _memStream = null;
                }
                if (_streamInReader != null)
                {
                    _streamInReader.Dispose();
                    _streamInReader = null;
                }
                if (_stringOutWriter != null)
                {
                    _stringOutWriter.Dispose();
                    _stringOutWriter = null;
                }
                if (_stringOutBuilder != null) _stringOutBuilder = null;
            }
        }

#endregion

    }
}